package org.JWCB.judge.service;

import cn.hutool.core.date.StopWatch;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.*;
import lombok.extern.slf4j.Slf4j;
import org.JWCB.api.domain.judge.dto.JudgeSubmitDto;
import org.JWCB.common.core.constants.JudgeConstants;
import org.JWCB.common.core.enums.CodeRunStatus;
import org.JWCB.judge.callback.DockerStartResultCallback;
import org.JWCB.judge.callback.StatisticsCallback;
import org.JWCB.judge.config.DockerSandBoxPool;
import org.JWCB.judge.domain.vo.CompileResult;
import org.JWCB.judge.domain.vo.SandBoxExecuteRes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


@Slf4j
@Service
public class SandBoxPoolService {
    @Value("${sandbox.limit.timeLimit:5000}")
    private Long timeLimit;

    @Autowired
    private DockerClient dockerClient;
    @Autowired
    private DockerSandBoxPool dockerSandBoxPool;


    /**
     * 执行java代码
     */
    public SandBoxExecuteRes exeJavaCode(JudgeSubmitDto judgeSubmitDto) {
        String containerId = dockerSandBoxPool.getContainer();
        String userCodeFile = createUserCodeFile(judgeSubmitDto.getUserCode(), containerId);
        // 编译代码
        CompileResult compileResult = compileCodeByDocker(containerId);
        if(!compileResult.isCompiled()) {
            deleteUserCodeFile(userCodeFile);
            dockerSandBoxPool.returnContainer(containerId);
            return SandBoxExecuteRes.fail(CodeRunStatus.COMPILE_FAILED,compileResult.getExeMessage());
        }
        try {
            return executeJavaCodeByDocker(judgeSubmitDto.getInputList(),judgeSubmitDto.getSpaceLimit(),judgeSubmitDto.getTimeLimit(),containerId,userCodeFile);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }




    /**
     * 为用户提交代码创建文件
     */
    private String createUserCodeFile(String userCode,String containerId) {
        String codeDir  = dockerSandBoxPool.getCodeDir(containerId);
        String userCodeFileName = codeDir + File.separator + JudgeConstants.JAVA_CLASS_FILE_NAME;
        if(FileUtil.exist(userCodeFileName)) {
            FileUtil.del(userCodeFileName);
        }
        FileUtil.writeString(userCode,userCodeFileName,"UTF-8");
        return codeDir;
    }


    private CompileResult compileCodeByDocker(String containerId) {
        String cmdId = createExecCmd(JudgeConstants.DOCKER_JAVAC_CMD,null,containerId);
        DockerStartResultCallback dockerStartResultCallback = new DockerStartResultCallback();
        CompileResult compileResult = new CompileResult();
        try {
            dockerClient.execStartCmd(cmdId)
                    .exec(dockerStartResultCallback)
                    .awaitCompletion();

            if(CodeRunStatus.FAILED.equals(dockerStartResultCallback.getCodeRunStatus())) {
                compileResult.setCompiled(false);
                compileResult.setExeMessage(dockerStartResultCallback.getErrorMessage());
            }else {
                compileResult.setCompiled(true);
            }
            return compileResult;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private String createExecCmd(String[] javaCmdArr,String inputArgs,String containerId) {
        if(!StrUtil.isEmpty(inputArgs)) {
            String[] inputArray = inputArgs.split(" ");// 入参
            javaCmdArr = ArrayUtil.append(JudgeConstants.DOCKER_JAVA_EXEC_CMD,inputArray);
        }

        ExecCreateCmdResponse createCmdResponse = dockerClient.execCreateCmd(containerId)
                .withCmd(javaCmdArr)
                .withAttachStderr(true)
                .withAttachStdin(true)
                .withAttachStdout(true)
                .exec();
        return createCmdResponse.getId();
    }



    private SandBoxExecuteRes executeJavaCodeByDocker(List<String> inputList, Long sLimit,Long tLimit,String containerId, String userCodeFile) throws IOException {
        List<String> outList = new ArrayList<>();
        long maxMemory = 0L;
        long maxUserTime = 0L;
        for (String inputArgs : inputList) {
            String cmdId = createExecCmd(JudgeConstants.DOCKER_JAVA_EXEC_CMD, inputArgs, containerId);
            StopWatch stopWatch = new StopWatch();
            StatsCmd statsCmd = dockerClient.statsCmd(containerId);
            StatisticsCallback statisticsCallback = new StatisticsCallback();
            statsCmd.exec(statisticsCallback);
            stopWatch.start();
            DockerStartResultCallback resultCallback = new DockerStartResultCallback();
            try {
                statisticsCallback.awaitFirstData(5, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                log.error("5s内未接受到流统计");
                throw new RuntimeException(e);
            }

            try {
                dockerClient.execStartCmd(cmdId)
                        .exec(resultCallback)
                        .awaitCompletion(Math.min(timeLimit,tLimit), TimeUnit.MILLISECONDS);
                stopWatch.stop();
                long userTime = stopWatch.getLastTaskTimeMillis(); //执行耗时
                maxUserTime = Math.max(userTime, maxUserTime);
                Long memory = statisticsCallback.getMaxMemory();
                if (memory != null && memory > 0) {
                    maxMemory = Math.max(maxMemory, memory);
                }
                if(CodeRunStatus.FAILED.equals(resultCallback.getCodeRunStatus())) {
                    // 运行失败
                    return SandBoxExecuteRes.fail(CodeRunStatus.FAILED,resultCallback.getErrorMessage());
                }
                if(StrUtil.isNotEmpty(resultCallback.getMessage())) {
                    outList.add(resultCallback.getMessage().trim());
                }
            } catch (Exception e) {
                log.info("执行中断{}",e.getMessage());
                dockerClient.killContainerCmd(cmdId).exec();
            } finally {
                if(stopWatch.isRunning()) {
                    stopWatch.stop();
                }
                statsCmd.close();
                resultCallback.close();
                statisticsCallback.close();
            }
        }
        deleteUserCodeFile(userCodeFile);
        dockerSandBoxPool.returnContainer(containerId);
        return assembleSandBoxResult(inputList, outList, maxMemory, maxUserTime, sLimit, tLimit);
    }

    private SandBoxExecuteRes assembleSandBoxResult(List<String> inputList, List<String> outList, long maxMemory, long maxUserTime,
                                                    long sLimit,long tLimit) {
        if(sLimit < maxMemory) {
            return SandBoxExecuteRes.fail(CodeRunStatus.OUT_OF_MEMORY,outList,maxMemory,maxUserTime);
        }else if(timeLimit < maxUserTime || tLimit < maxUserTime) {
            return SandBoxExecuteRes.fail(CodeRunStatus.OUT_OF_TIME,outList,maxMemory,maxUserTime);
        }
        if(inputList.size() != outList.size()) {
            return SandBoxExecuteRes.fail(CodeRunStatus.NOT_PASS_ALL_CASE,outList,maxMemory,maxUserTime);
        }
        return SandBoxExecuteRes.success(CodeRunStatus.SUCCESS,outList,maxMemory,maxUserTime);
    }

    /**
     * 清理文件
     */
    private void deleteUserCodeFile(String userCodeDir) {
        FileUtil.del(userCodeDir);
    }


}
